﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;

namespace RVBike
{
    class StageManager : Singleton<StageManager>
    {
        private string _rootPath;
        private List<RVStage> _stages;

        public StageManager()
        {
            _rootPath = PathUtility.Instance().MoviesDir;
            _stages = new List<RVStage>();

            if (!Directory.Exists(_rootPath))
            {
                string error = String.Format("Stage folder \"{0}\" not exist", _rootPath);
                Logger.Instance().Fatal(error);
                throw new System.ArgumentException(error);
            }

            ScanStageRootFolder(_rootPath);
        }

        

        private void ScanStageRootFolder(string path)
        {
            Logger.Instance().Info("Start scanning stage folder ...");
            foreach (DirectoryInfo subDI in (new DirectoryInfo(path)).GetDirectories())
            {
                ScanStageFolder(subDI);
            }
            Logger.Instance().Info("Finish scanning stage folder ...");
        }

        private void ScanStageFolder(DirectoryInfo di)
        {
            Logger.Instance().Info(String.Format("Load stage : {0}", di.Name));

            if (!CheckStageStructure(di)) 
            {
                return;
            }

            try
            {
                _stages.Add(new RVStage(di));
            }
            catch (Exception e)
            {
                Logger.Instance().Error(String.Format("Failed to crate stage for {0} : ", di.Name) + e);
            }
        }

        /**
         * A valid stage folder must have a stage.xml file to descript its content
         */
        private bool CheckStageStructure(DirectoryInfo di)
        {
            // Check stage.xml first
            string pathStageXML = di.FullName + "\\stage.xml";
            if (!File.Exists(pathStageXML))
            {
                Logger.Instance().Error("Invalid stage structure : lack of stage.xml");
                return false;
            }

            // Parse stage.xml and check whether there is any file missed.
            try
            {
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(pathStageXML);

                XmlElement root = xmlDoc.DocumentElement;

                // check cover
                if (!root.HasAttribute("cover"))
                {
                    Logger.Instance().Error("Failed to parse stage.xml : lack of \"cover\" attribute in root node");
                    return false;
                }
                else
                {
                    string pathCover = di.FullName + "\\" + root.GetAttribute("cover");
                    if (!File.Exists(pathCover))
                    {
                        Logger.Instance().Error(String.Format("Failed to parse stage.xml : cannot find cover image[{0}]", pathCover));
                        return false;
                    }
                }

                // check name
                if (!root.HasAttribute("name"))
                {
                    Logger.Instance().Error("Failed to parse stage.xml : lack of \"name\" attribute in root node");
                    return false;
                }

                // check description
                if (!root.HasAttribute("description"))
                {
                    Logger.Instance().Error("Failed to parse stage.xml : lack of \"description\" attribute in root node");
                    return false;
                }

                // check map elements
                XmlNodeList maps = root.SelectNodes("/stage/maps/map");
                if (maps.Count == 0)
                {
                    Logger.Instance().Error("Failed to parse stage.xml : lack of map elements");
                    return false;
                }

                // check map attributes
                XmlElement mapRoot = (XmlElement)root.SelectSingleNode("/stage/maps");
                if (!mapRoot.HasAttribute("gps_start"))
                {
                    Logger.Instance().Error("Failed to parse stage.xml : lack of \"gps_start\" attribute in maps node");
                    return false;
                }

                if (!mapRoot.HasAttribute("gps_end"))
                {
                    Logger.Instance().Error("Failed to parse stage.xml : lack of \"gps_end\" attribute in maps node");
                    return false;
                }

                for (int index = 0; index < maps.Count; ++index)
                {
                    XmlElement map = (XmlElement)maps.Item(index);

                    // check level
                    if (!map.HasAttribute("level"))
                    {
                        Logger.Instance().Error("Failed to parse stage.xml : lack of \"level\" attribute in map node");
                        return false;
                    }

                    // check file
                    if (!map.HasAttribute("file"))
                    {
                        Logger.Instance().Error("Failed to parse stage.xml : lack of \"file\" attribute in map node");
                        return false;
                    }
                    else
                    {
                        string pathMap = di.FullName + "\\" + map.GetAttribute("file");
                        if (!File.Exists(pathMap))
                        {
                            Logger.Instance().Error(String.Format("Failed to parse stage.xml : cannot find map file[{0}]", pathMap));
                            return false;
                        }
                    }

                    // check left
                    if (!map.HasAttribute("left"))
                    {
                        Logger.Instance().Error("Failed to parse stage.xml : lack of \"left\" attribute in map node");
                        return false;
                    }

                    // check top
                    if (!map.HasAttribute("top"))
                    {
                        Logger.Instance().Error("Failed to parse stage.xml : lack of \"top\" attribute in map node");
                        return false;
                    }

                    // check width
                    if (!map.HasAttribute("width"))
                    {
                        Logger.Instance().Error("Failed to parse stage.xml : lack of \"width\" attribute in map node");
                        return false;
                    }

                    // check height
                    if (!map.HasAttribute("height"))
                    {
                        Logger.Instance().Error("Failed to parse stage.xml : lack of \"height\" attribute in map node");
                        return false;
                    }

                    // check maps_start
                    if (!map.HasAttribute("map_start"))
                    {
                        Logger.Instance().Error("Failed to parse stage.xml : lack of \"map_start\" attribute in map node");
                        return false;
                    }

                    // check map_end
                    if (!map.HasAttribute("map_end"))
                    {
                        Logger.Instance().Error("Failed to parse stage.xml : lack of \"map_end\" attribute in map node");
                        return false;
                    }

                    // check scale
                    if (!map.HasAttribute("scale"))
                    {
                        Logger.Instance().Error("Failed to parse stage.xml : lack of \"scale\" attribute in map node");
                        return false;
                    }
                }

                // check movies elements
                XmlNodeList movies = root.SelectNodes("/stage/movies/movie");
                if (movies.Count == 0)
                {
                    Logger.Instance().Error("Failed to parse stage.xml : lack of movie elements");
                    return false;
                }

                for (int index = 0; index < movies.Count; ++index)
                {
                    XmlElement movie = (XmlElement)movies.Item(index);

                    // check id
                    if (!movie.HasAttribute("id"))
                    {
                        Logger.Instance().Error("Failed to parse stage.xml : lack of \"id\" attribute in movie node");
                        return false;
                    }

                    // check file
                    if (!movie.HasAttribute("file"))
                    {
                        Logger.Instance().Error("Failed to parse stage.xml : lack of \"file\" attribute in movie node");
                        return false;
                    }
                    else
                    {
                        string pathMovie = di.FullName + "\\" + movie.GetAttribute("file");
                        if (!File.Exists(pathMovie))
                        {
                            Logger.Instance().Error(String.Format("Failed to parse stage.xml : cannot find movie file[{0}]", pathMovie));
                            return false;
                        }
                    }

                    // check length
                    if (!movie.HasAttribute("mileage"))
                    {
                        Logger.Instance().Error("Failed to parse stage.xml : lack of \"length\" attribute in movie node");
                        return false;
                    }

                    // check metafile
                    if (!movie.HasAttribute("metafile"))
                    {
                        Logger.Instance().Error("Failed to parse stage.xml : lack of \"metafile\" attribute in movie node");
                        return false;
                    }
                    else
                    {
                        string pathMetadata = di.FullName + "\\" + movie.GetAttribute("metafile");
                        if (!File.Exists(pathMetadata))
                        {
                            Logger.Instance().Error(String.Format("Failed to parse stage.xml : cannot find movie metadata file[{0}]", pathMetadata));
                            return false;
                        }
                    }
                }

                // check languages elements
                XmlNodeList languages = root.SelectNodes("/stage/languages/language");
                for (int index = 0; index < languages.Count; ++index)
                {
                    XmlElement language = (XmlElement)languages.Item(index);

                    // check locale
                    if (!language.HasAttribute("locale"))
                    {
                        Logger.Instance().Error("Failed to parse stage.xml : lack of \"locale\" attribute in language node");
                        return false;
                    }

                    // check file
                    if (!language.HasAttribute("file"))
                    {
                        Logger.Instance().Error("Failed to parse stage.xml : lack of \"file\" attribute in language node");
                        return false;
                    }
                    else
                    {
                        string pathLanguage = di.FullName + "\\" + language.GetAttribute("file");
                        if (!File.Exists(pathLanguage))
                        {
                            Logger.Instance().Error(String.Format("Failed to parse stage.xml : cannot find language file[{0}]", pathLanguage));
                            return false;
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Logger.Instance().Error("Failed to parse stage.xml : " + e);
                return false;
            }

            return true;
        }

        public int StageCount
        {
            get
            {
                return _stages.Count;
            }
        }
        public RVStage GetStage(string name)
        {
            RVStage stage = null;
            foreach (RVStage s in _stages)
            {
                if (name == s.Name)
                {
                    stage = s;
                    break;
                }
            }
            return stage;
        }
        public RVStage GetStage(int index)
        {
            if (index < 0 || index >= _stages.Count)
            {
                throw new System.IndexOutOfRangeException();
            }

            return _stages[index];
        }
    }
}
